/*
 * Copyright 2011 Mark Davidson.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.dctmcommons.impl;

import java.util.ArrayList;
import java.util.List;

import org.dctmcommons.ICoreObjectInfo;
import org.dctmcommons.ICoreObjectInfoCallback;
import org.dctmcommons.IFilteredCoreObjectInfoCallback;
import org.dctmcommons.IObjectHelper;
import org.dctmcommons.IParamCheck;
import org.dctmcommons.IPathBuilder;
import org.dctmcommons.IStringHelper;

import com.documentum.fc.client.IDfACL;
import com.documentum.fc.client.IDfCollection;
import com.documentum.fc.client.IDfFolder;
import com.documentum.fc.client.IDfQuery;
import com.documentum.fc.client.IDfSession;
import com.documentum.fc.client.IDfSysObject;
import com.documentum.fc.client.IDfType;
import com.documentum.fc.client.IDfValidator;
import com.documentum.fc.client.IDfValueAssistance;
import com.documentum.fc.common.DfException;
import com.documentum.fc.common.DfLogger;
import com.documentum.fc.common.IDfList;

import static org.dctmcommons.DctmCommonsConstants.*;
import static com.documentum.fc.common.DfDocbaseConstants.*;

/**
 * Object-related helper methods.
 */
public class ObjectHelper implements IObjectHelper {
	
	/** Token for object id. */
	private static String KEY_OBJECT_ID = "OBJECT_ID";
	
	/** Token for path. */
	private static String KEY_PATH = "PATH";
	
	/** DQL for getting primary folder info for an object. */
	private static final String DQL_GET_PRIMARY_FOLDER_INFO =
		"select r_object_id, r_folder_path, object_name " +
		   "from dm_folder where r_object_id in " +
		   "(select i_folder_id from dm_sysobject " +
		   "where r_object_id = '" + KEY_OBJECT_ID +"')";
	
	/** DQL for getting a list of cabinet info. */
	private static final String DQL_GET_CABINETS_INFO =
		"select object_name, r_object_id, r_object_type, acl_name, " +
		"acl_domain, r_content_size, a_content_type, r_link_cnt, " +
		"r_is_virtual_doc " +
        "from dm_cabinet " +
        "order by object_name, r_object_id, r_object_type, acl_name, " +
        "acl_domain, r_content_size, a_content_type, r_link_cnt, " +
        "r_is_virtual_doc";
	
	/** DQL for getting a list of sysobjects at a specified path. */
	private static final String DQL_GET_OBJECT_INFO_BY_PATH =
		"select object_name, r_object_id, r_object_type, acl_name, " +
		"acl_domain, r_content_size, a_content_type, r_link_cnt, " +
		"r_is_virtual_doc " +
        "from dm_sysobject " +
        "where folder('" + KEY_PATH + "') " +
        "order by object_name, r_object_id, r_object_type, acl_name, " +
        "acl_domain, r_content_size, a_content_type, r_link_cnt, " +
        "r_is_virtual_doc";
	
	/** The parameter check instance. */
	private static final IParamCheck paramCheck = ParamCheck.getInstance();
	
	/**
	 * Default constructor.
	 */
	private ObjectHelper() {}
	
	/**
	 * Factory method.
	 * @return The object helper instance
	 */
	public static IObjectHelper getInstance() {
		return new ObjectHelper();
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.dctmcommons.IObjectHelper#isMajorVersion(com.documentum.fc.client.IDfSysObject)
	 */
	@Override
	public boolean isMajorVersion(final IDfSysObject sysobject)
		throws DfException {
		
		paramCheck.checkObject(sysobject, "sysobject");
		
		final String versionLabel = sysobject.getImplicitVersionLabel();
		final int length = versionLabel.length();
		final String suffix = versionLabel.substring(length - 2, length);
		
		return ".0".equals(suffix);
	}
	
	/* (non-Javadoc)
	 * @see org.dctmcommons.IObjectHelper#getPrimaryFolderDetails(com.documentum.fc.client.IDfSession, java.lang.String)
	 */
	@Override
	public String[] getPrimaryFolderDetails(final IDfSession session,
											final String objectId)
		throws DfException {
		
		final String dql = DQL_GET_PRIMARY_FOLDER_INFO.replaceFirst("OBJECT_ID",
																	objectId);
		
		return QueryHelper.getInstance().queryForSingleStringValues(
														session,
														dql,
														IDfQuery.DF_READ_QUERY);
	}
	
	/**
	 * Maps an {@link IDfCollection} to a list of {@link ICoreObjectInfo}
	 * instance.
	 * @param results The results collection
	 * @param path The parent path
	 * @return The list of {@link ICoreObjectInfo} instances
	 * @throws DfException The repository exception
	 */
	private List<ICoreObjectInfo> transformAll(final IDfCollection results,
											   final String path)
		throws DfException {
		
		paramCheck.checkString(path, "path");
		
		final List<ICoreObjectInfo> list = new ArrayList<ICoreObjectInfo>();
		
		if (results != null) {
			while (results.next()) {
				list.add(transform(results, path));
			}
		}
		
		return list.size() == 0 ? null : list;
	}
	
	/**
	 * Maps an {@link IDfCollection} row to an {@link ICoreObjectInfo}.
	 * instance.
	 * @param row The results collection
	 * @param path The parent path
	 * @return The {@link ICoreObjectInfo} instance
	 * @throws DfException The repository exception
	 */
	private ICoreObjectInfo transform(final IDfCollection row,
									  final String path)
		throws DfException {
		
		paramCheck.checkObject(row, "row");
		paramCheck.checkString(path, "path");
		
		final String objectName = row.getString(OBJECT_NAME);
		
		CoreObjectInfo info = CoreObjectInfo.getInstance()
					.setAclName(row.getString(ACL_NAME))
					.setContentSize(row.getInt(R_CONTENT_SIZE))
					.setContentType(row.getString(A_CONTENT_TYPE))
					.setFolderPath(path)
					.setLinkCount(row.getInt(R_LINK_CNT))
					.setObjectId(row.getString(R_OBJECT_ID))
					.setObjectName(objectName)
					.setObjectPath(path + PATH_SEPARATOR + objectName)
					.setObjectType(row.getString(R_OBJECT_TYPE))
					.setVirtualDocument(row.getBoolean(R_IS_VIRTUAL_DOC));
		
		if (DfLogger.isTraceEnabled(this)) {
			DfLogger.trace(this, info.toString(), null, null);
		}
		
		return info;
	}
	
	/**
	 * Walks child repository folders from the specified base path.
	 * @param session The session
	 * @param path the path to start walking from
	 * @param walkByDepth If walkByDepth is true then the method will attempt
	 * to walk as deep as possible. If walkByDepth is false then the method
	 * will attempt to walk the contents of the current folder before
	 * attempting to move to the next folder level down. 
	 * @param callback The callback object. To filter the objects by type (e.g.
	 * you may want to be notified of just dm_folder objects), pass in an
	 * instance of {@link IFilteredTypedObjectCallback}.
	 * @throws DfException The repository exception
	 */
	@Override
	public void walk(final IDfSession session,
					 final String path,
					 final boolean walkByDepth,
					 final ICoreObjectInfoCallback callback)
			throws DfException {

		paramCheck.checkObject(session, "session");
		paramCheck.checkString(path, "path");
		paramCheck.checkObject(callback, "callback");
		
		String dql = null;
		
		if (path == PATH_SEPARATOR) {
			dql = DQL_GET_CABINETS_INFO;
		} else {
			dql = DQL_GET_OBJECT_INFO_BY_PATH.replaceFirst(KEY_PATH, path);
		}
		
		final IDfCollection results = QueryHelper.getInstance().query(
													session,
													dql,
													IDfQuery.DF_READ_QUERY);
		final List<ICoreObjectInfo> coreObjects = transformAll(results, path);
		
		if (coreObjects != null) {
			for (final ICoreObjectInfo coreObject : coreObjects) {
				// identify folder-type containers with child elements
                // that are not virtual docs
				if (isContainerOfObjects(coreObject)) {
					// walk by depth
					if (walkByDepth) {
						walk(session,
							 coreObject.getObjectPath(),
							 walkByDepth,
							 callback);
					}
				}
					
				// handle callback
				if (callback instanceof IFilteredCoreObjectInfoCallback) {
					final IFilteredCoreObjectInfoCallback filteredCallback =
						(IFilteredCoreObjectInfoCallback) callback;
					final List<String> validTypes =
						filteredCallback.getIncludedTypes();
					if (validTypes != null &&
						validTypes.contains(coreObject.getObjectType())) {
						filteredCallback.read(coreObject);
					}
				} else {
					callback.read(coreObject);
				}
	                
                // identify folder-type containers with child elements
                // that are not virtual docs
				if (isContainerOfObjects(coreObject)) {
                    // walk by width
                    if(!walkByDepth) {
                    	walk(session,
                    		 coreObject.getObjectPath(),
                    		 walkByDepth,
                    		 callback);
                    }
                }
			}
		}
	}

	/**
	 * Identifies folders that contain child objects
	 * @param coreObject The core object
	 * @return The outcome (true = contains child objects)
	 */
	private boolean isContainerOfObjects(final ICoreObjectInfo coreObject) {
		return coreObject.getLinkCount() > 0 && !coreObject.isVirtualDoc();
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.dctmcommons.IObjectHelper#mkdirs(com.documentum.fc.client.IDfSession, java.lang.String, java.lang.String, com.documentum.fc.client.IDfACL)
	 */
	@Override
	public IDfFolder mkdirs(final IDfSession session,
							final String path,
							final String type,
							final IDfACL acl) throws DfException {
		
		paramCheck.checkObject(session, "session");
		paramCheck.checkString(path, "path");
		
		final IStringHelper sh = StringHelper.getInstance();
		final String[] folderNames = sh.splitPath(path, false);
		
		final IPathBuilder builder = PathBuilder
											.from(PATH_SEPARATOR)
											.setCheckBasePathExists(true)
											.setCheckTargetFolderExists(true)
											.setDefaultType(type)
											.setDefaultAcl(acl);
		for (final String folderName : folderNames) {
			builder.add(folderName);
		}
		
		final IDfFolder[] folders = builder.build(session);
		
		if (folders != null && folders.length > 0) {
			return folders[folders.length - 1];
		} else {
			return null;
		}
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.dctmcommons.IObjectHelper#mkdirs(com.documentum.fc.client.IDfSession, java.lang.String, java.lang.String, java.lang.String, com.documentum.fc.client.IDfACL)
	 */
	@Override
	public IDfFolder mkdirs(final IDfSession session,
							final String basePath,
							final String relativePathToCreate,
							final String type,
							final IDfACL acl)
			throws DfException {
		
		paramCheck.checkObject(session, "session");
		paramCheck.checkString(basePath, "basePath");
		paramCheck.checkString(relativePathToCreate, "relativePathToCreate");
		
		final IStringHelper sh = StringHelper.getInstance();
		final String[] folderNames = sh.splitPath(relativePathToCreate, false);
		
		final IPathBuilder builder = PathBuilder
											.from(basePath)
											.setCheckBasePathExists(false)
											.setCheckTargetFolderExists(false)
											.setDefaultType(type)
											.setDefaultAcl(acl);
		for (final String folderName : folderNames) {
			builder.add(folderName);
		}

		final IDfFolder[] folders = builder.build(session);
		
		if (folders != null && folders.length > 0) {
			return folders[folders.length - 1];
		} else {
			return null;
		}
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.dctmcommons.IObjectHelper#getValueAssistance(com.documentum.fc.client.IDfSession, java.lang.String, java.lang.String)
	 */
	public String[] getValueAssistance(final IDfSession session,
									   final String typeName,
									   final String attribName)
		throws DfException {
		
		paramCheck.checkObject(session, "session");
		paramCheck.checkString(typeName, "typeName");
		paramCheck.checkString(attribName, "attribName");
		
		String[] values = null;
		
		final IDfType type = session.getType(typeName);
		if (type != null) {
			final IDfValidator validator = type.getTypeValidator(null,
																 null);
			if (validator != null) {
				final IDfValueAssistance va = validator.getValueAssistance(
																attribName,
																null);
				if (va != null) {
					final IDfList list = va.getActualValues();
					if (list != null) {
						values = new String[list.getCount()];
						for (int i = 0; i < list.getCount(); i++)
							values[i] = list.getString(i);
					}
				}
			}
		}
		
		return values;
	}
}
